package pl.edu.agh.gp;

import org.jgap.InvalidConfigurationException;
import org.jgap.gp.CommandGene;
import org.jgap.gp.GPFitnessFunction;
import org.jgap.gp.function.*;
import org.jgap.gp.impl.*;
import org.jgap.gp.terminal.False;
import org.jgap.gp.terminal.Terminal;
import org.jgap.gp.terminal.True;
import org.jgap.gp.terminal.Variable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import pl.edu.agh.gp.nodes.math.*;
import pl.edu.agh.gp.nodes.math.ExpMovAvg;
import pl.edu.agh.gp.nodes.stock.RSI;
import pl.edu.agh.types.VariableType;
import pl.edu.agh.utils.BranchTypingCrossLogger;

import java.util.*;

import static org.jgap.gp.CommandGene.*;
import static pl.edu.agh.types.VariableType.*;

@Component
public class StockGPConfiguration {
    private @Value("${evolutionsNo}") int evolutionsNo;
    private @Value("${maxInitDepth}") int maxInitDepth;
    private @Value("${populationSize}") int populationSize;
    private @Value("${maxCrossoverDepth}") int maxCrossoverDepth;
    private @Value("${strictProgramCreation}") boolean strictProgramCreation;
    private @Value("${maxNodes}") int maxNodes;
    private @Value("${verboseOutput}") Boolean verboseOutput;
    private @Value("${crossoverProb}") float crossoverProb;
    private @Value("${mutationProb}") float mutationProb;
    private @Value("${terminalsSize}") int terminalsSize;
    private @Value("${terminalMin}") double terminalMin;
    private @Value("${terminalMax}") double terminalMax;
    private @Value("${newChromsPercent}") double newChromsPercent;
    private @Value("${maxArity}") int maxArity;
    private @Value("${tournamentSize}") int tournamentSize;

    private Variable closePrice;
    private Variable openPrice;
    private Variable highPrice;
    private Variable lowPrice;
    private Variable prices;
    private Variable volume;

    public GPGenotype create(GPConfiguration conf) throws InvalidConfigurationException{
        Class[] types = {
                BooleanClass, DoubleClass
        };
        Class[][] argTypes = {
                {}, {}
        };

        Random random = new Random();

        CommandGene[] partialNodeSet = {
                openPrice = Variable.create(conf, "OpenPrice", DoubleClass),
                highPrice = Variable.create(conf, "HighPrice", DoubleClass),
                lowPrice = Variable.create(conf, "LowPrice", DoubleClass),
                closePrice = Variable.create(conf, "ClosePrice", DoubleClass),
                volume = Variable.create(conf, "Volume", DoubleClass),
                prices = Variable.create(conf, "Prices", List.class),

                // Relational
                new LesserThan(conf, DoubleClass),
                new GreaterThan(conf, DoubleClass),
                new LesserThan(conf, BooleanClass),
                new GreaterThan(conf, BooleanClass),
                new Equals(conf, BooleanClass),
                new And(conf),
                new Or(conf),
                new Not(conf),
                new Xor(conf),
                new If(conf, DoubleClass),
                new If(conf, BooleanClass),
                new IfElse(conf, DoubleClass),
                new IfElse(conf, BooleanClass),

                // Math
                new Abs(conf, DoubleClass),
                new Add(conf, DoubleClass),
                new Subtract(conf, DoubleClass),
                new Multiply(conf, DoubleClass),
                new Max(conf, DoubleClass),
                new Min(conf, DoubleClass),
                new Sine(conf, DoubleClass),
                new Exp(conf, DoubleClass),
                new Pow(conf, DoubleClass),
                new ArcCosine(conf, DoubleClass),
                new ArcSine(conf, DoubleClass),
                new ArcTangent(conf, DoubleClass),
                new Ceil(conf, DoubleClass),
                new Cosine(conf, DoubleClass),
                new Floor(conf, DoubleClass),
                new Log(conf,DoubleClass),
                new Modulo(conf,DoubleClass),
                new Tangent(conf,DoubleClass),
                new Divide(conf, DoubleClass),
                new Increment(conf, DoubleClass),
                new Round(conf, DoubleClass),

                // self-definded
                new StandardDeviation(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new StandardDeviation(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new Mean(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new Mean(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new ExpMovAvg(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new ExpMovAvg(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new Variance(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new Variance(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new Skweness(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new Skweness(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new Kurtosis(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new Kurtosis(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new Percentile(conf, random.nextInt(maxArity - 2) + 2, maxArity, DoubleClass),
                new Percentile(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),
                new RSI(conf, random.nextInt(maxArity - 2) + 2, maxArity, List.class),

                new True(conf),
                new False(conf)
        };

        List<CommandGene> nodesWithTerminals = new LinkedList<CommandGene>();
        for(CommandGene gene: partialNodeSet){
            nodesWithTerminals.add(gene);
        }
        for(int i = 0; i < terminalsSize; i++){
            nodesWithTerminals.add(new Terminal(conf, DoubleClass, terminalMin, terminalMax, false));
        }
        CommandGene[][] nodeSets = {nodesWithTerminals.toArray(new CommandGene[]{}), nodesWithTerminals.toArray(new CommandGene[]{})};

        return GPGenotype.randomInitialGenotype(conf, types, argTypes, nodeSets, maxNodes, verboseOutput);
    }

    public GPConfiguration getConfiguration(GPFitnessFunction gpFitnessFunction) throws InvalidConfigurationException {
        GPConfiguration.reset();
        GPConfiguration conf = new GPConfiguration();
        conf.setGPFitnessEvaluator(new DefaultGPFitnessEvaluator());
        conf.setMaxInitDepth(maxInitDepth);
        conf.setPopulationSize(populationSize);
        conf.setMaxCrossoverDepth(maxCrossoverDepth);
        conf.setFitnessFunction(gpFitnessFunction);
        conf.setUseProgramCache(true);
        conf.setStrictProgramCreation(strictProgramCreation);
        conf.setCrossoverProb(crossoverProb);
        conf.setMutationProb(mutationProb);
        conf.setCrossoverProb(crossoverProb);
        // requirement that rep + cross = 1.0
        conf.setReproductionProb(1.0f - crossoverProb);
        // percentage of the population that will be filled with new individuals during evolution
        conf.setNewChromsPercent(newChromsPercent);
        conf.setCrossoverMethod(new BranchTypingCrossLogger(conf));
        // succession
        conf.setSelectionMethod(new TournamentSelector(tournamentSize));

        return conf;
    }

    public int getEvolutionsNo() {
        return evolutionsNo;
    }

    public Map<VariableType, Variable> getVariables() {
        Map<VariableType, Variable> variables = new HashMap<VariableType, Variable>();
        variables.put(OPEN, openPrice);
        variables.put(HIGH, highPrice);
        variables.put(LOW, lowPrice);
        variables.put(CLOSE, closePrice);
        variables.put(PRICES, prices);
        variables.put(VOLUME, volume);
        return variables;
    }
}
